 /*******************************************************************************
  * Copyright (c) 2001, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal.views.properties.tabbed.view;

 import org.eclipse.jface.resource.JFaceResources;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.accessibility.ACC;
 import org.eclipse.swt.accessibility.Accessible;
 import org.eclipse.swt.accessibility.AccessibleAdapter;
 import org.eclipse.swt.accessibility.AccessibleControlAdapter;
 import org.eclipse.swt.accessibility.AccessibleControlEvent;
 import org.eclipse.swt.accessibility.AccessibleEvent;
 import org.eclipse.swt.events.ControlAdapter;
 import org.eclipse.swt.events.ControlEvent;
 import org.eclipse.swt.events.FocusEvent;
 import org.eclipse.swt.events.FocusListener;
 import org.eclipse.swt.events.MouseAdapter;
 import org.eclipse.swt.events.MouseEvent;
 import org.eclipse.swt.events.MouseMoveListener;
 import org.eclipse.swt.events.MouseTrackAdapter;
 import org.eclipse.swt.events.PaintEvent;
 import org.eclipse.swt.events.PaintListener;
 import org.eclipse.swt.events.TraverseEvent;
 import org.eclipse.swt.events.TraverseListener;
 import org.eclipse.swt.graphics.Color;
 import org.eclipse.swt.graphics.FontMetrics;
 import org.eclipse.swt.graphics.GC;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.graphics.RGB;
 import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.layout.FormAttachment;
 import org.eclipse.swt.layout.FormData;
 import org.eclipse.swt.layout.FormLayout;
 import org.eclipse.swt.widgets.Canvas;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.Listener;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.ui.forms.FormColors;
 import org.eclipse.ui.internal.views.properties.tabbed.l10n.TabbedPropertyMessages;
 import org.eclipse.ui.views.properties.tabbed.ITabItem;
 import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetWidgetFactory;


 /**
  * Shows the list of tabs in the tabbed property sheet page.
  *
  * @author Anthony Hunter
  */
 public class TabbedPropertyList
     extends Composite {

     private static final ListElement[] ELEMENTS_EMPTY = new ListElement[0];

     protected static final int NONE = -1;

     protected static final int INDENT = 7;

     private ListElement[] elements;

     private int selectedElementIndex = NONE;

     private int topVisibleIndex = NONE;

     private int bottomVisibleIndex = NONE;

     private TopNavigationElement topNavigationElement;

     private BottomNavigationElement bottomNavigationElement;

     private int widestLabelIndex = NONE;

     private int tabsThatFitInComposite = NONE;

     private Color widgetForeground;

     private Color widgetBackground;

     private Color widgetNormalShadow;

     private Color widgetDarkShadow;

     private Color listBackground;

     private Color hoverGradientStart;

     private Color hoverGradientEnd;

     private Color defaultGradientStart;

     private Color defaultGradientEnd;

     private Color indentedDefaultBackground;

     private Color indentedHoverBackground;

     private Color navigationElementShadowStroke;

     private Color bottomNavigationElementShadowStroke1;

     private Color bottomNavigationElementShadowStroke2;

     private TabbedPropertySheetWidgetFactory factory;

     /**
      * One of the tabs in the tabbed property list.
      */
     public class ListElement extends Canvas {

         private ITabItem tab;

         private int index;

         private boolean selected;

         private boolean hover;

         /**
          * Constructor for ListElement.
          *
          * @param parent
          * the parent Composite.
          * @param tab
          * the tab item for the element.
          * @param index
          * the index in the list.
          */
         public ListElement(Composite parent, final ITabItem tab, int index) {
             super(parent, SWT.NO_FOCUS);
             this.tab = tab;
             hover = false;
             selected = false;
             this.index = index;

             addPaintListener(new PaintListener() {

                 public void paintControl(PaintEvent e) {
                     paint(e);
                 }
             });
             addMouseListener(new MouseAdapter() {

                 public void mouseUp(MouseEvent e) {
                     if (!selected) {
                         select(getIndex(ListElement.this));
                         /*
                          * We set focus to the tabbed property composite so that
                          * focus is moved to the appropriate widget in the
                          * section.
                          */
                         Composite tabbedPropertyComposite = getParent();
                         while (!(tabbedPropertyComposite instanceof TabbedPropertyComposite)) {
                             tabbedPropertyComposite = tabbedPropertyComposite
                                 .getParent();
                         }
                         tabbedPropertyComposite.setFocus();
                     }
                 }
             });
             addMouseMoveListener(new MouseMoveListener() {

                 public void mouseMove(MouseEvent e) {
                     if (!hover) {
                         hover = true;
                         redraw();
                     }
                 }
             });
             addMouseTrackListener(new MouseTrackAdapter() {

                 public void mouseExit(MouseEvent e) {
                     hover = false;
                     redraw();
                 }
             });
         }

         /**
          * Set selected value for this element.
          *
          * @param selected
          * the selected value.
          */
         public void setSelected(boolean selected) {
             this.selected = selected;
             redraw();
         }

         /**
          * Paint the element.
          *
          * @param e
          * the paint event.
          */
         private void paint(PaintEvent e) {
             /*
              * draw the top two lines of the tab, same for selected, hover and
              * default
              */
             Rectangle bounds = getBounds();
             e.gc.setForeground(widgetNormalShadow);
             e.gc.drawLine(0, 0, bounds.width - 1, 0);
             e.gc.setForeground(listBackground);
             e.gc.drawLine(0, 1, bounds.width - 1, 1);

             /* draw the fill in the tab */
             if (selected) {
                 e.gc.setBackground(listBackground);
                 e.gc.fillRectangle(0, 2, bounds.width, bounds.height - 1);
             } else if (hover && tab.isIndented()) {
                 e.gc.setBackground(indentedHoverBackground);
                 e.gc.fillRectangle(0, 2, bounds.width - 1, bounds.height - 1);
             } else if (hover) {
                 e.gc.setForeground(hoverGradientStart);
                 e.gc.setBackground(hoverGradientEnd);
                 e.gc.fillGradientRectangle(0, 2, bounds.width - 1,
                         bounds.height - 1, true);
             } else if (tab.isIndented()) {
                 e.gc.setBackground(indentedDefaultBackground);
                 e.gc.fillRectangle(0, 2, bounds.width - 1, bounds.height - 1);
             } else {
                 e.gc.setForeground(defaultGradientStart);
                 e.gc.setBackground(defaultGradientEnd);
                 e.gc.fillGradientRectangle(0, 2, bounds.width - 1,
                         bounds.height - 1, true);
             }

             if (!selected) {
                 e.gc.setForeground(widgetNormalShadow);
                 e.gc.drawLine(bounds.width - 1, 1, bounds.width - 1,
                         bounds.height + 1);
             }

             int textIndent = INDENT;
             FontMetrics fm = e.gc.getFontMetrics();
             int height = fm.getHeight();
             int textMiddle = (bounds.height - height) / 2;

             if (selected && tab.getImage() != null
                 && !tab.getImage().isDisposed()) {
                 /* draw the icon for the selected tab */
                 if (tab.isIndented()) {
                     textIndent = textIndent + INDENT;
                 } else {
                     textIndent = textIndent - 3;
                 }
                 e.gc.drawImage(tab.getImage(), textIndent, textMiddle - 1);
                 textIndent = textIndent + 16 + 5;
             } else if (tab.isIndented()) {
                 textIndent = textIndent + INDENT;
             }

             /* draw the text */
             e.gc.setForeground(widgetForeground);
             if (selected) {
                 /* selected tab is bold font */
                 e.gc.setFont(JFaceResources.getFontRegistry().getBold(
                         JFaceResources.DEFAULT_FONT));
             }
             e.gc.drawText(tab.getText(), textIndent, textMiddle, true);

             /* draw the bottom line on the tab for selected and default */
             if (!hover) {
                 e.gc.setForeground(listBackground);
                 e.gc.drawLine(0, bounds.height - 1, bounds.width - 2,
                         bounds.height - 1);
             }
         }

         /**
          * Get the tab item.
          *
          * @return the tab item.
          */
         public ITabItem getTabItem() {
             return tab;
         }

         public String toString() {
             return tab.getText();
         }
     }

     /**
      * The top navigation element in the tabbed property list. It looks like a
      * scroll button when scrolling is needed or is just a spacer when no
      * scrolling is required.
      */
     public class TopNavigationElement extends Canvas {

         /**
          * Constructor for TopNavigationElement.
          *
          * @param parent
          * the parent Composite.
          */
         public TopNavigationElement(Composite parent) {
             super(parent, SWT.NO_FOCUS);
             addPaintListener(new PaintListener() {

                 public void paintControl(PaintEvent e) {
                     paint(e);
                 }
             });
             addMouseListener(new MouseAdapter() {

                 public void mouseUp(MouseEvent e) {
                     if (isUpScrollRequired()) {
                         bottomVisibleIndex--;
                         if (topVisibleIndex != 0) {
                             topVisibleIndex--;
                         }
                         layoutTabs();
                         topNavigationElement.redraw();
                         bottomNavigationElement.redraw();
                     }
                 }
             });
         }

         /**
          * Paint the element.
          *
          * @param e
          * the paint event.
          */
         private void paint(PaintEvent e) {
             e.gc.setBackground(widgetBackground);
             e.gc.setForeground(widgetForeground);
             Rectangle bounds = getBounds();

             if (elements.length != 0) {
                 e.gc.fillRectangle(0, 0, bounds.width, bounds.height);
                 e.gc.setForeground(widgetNormalShadow);
                 e.gc.drawLine(bounds.width - 1, 0, bounds.width - 1,
                     bounds.height - 1);
             } else {
                 e.gc.setBackground(listBackground);
                 e.gc.fillRectangle(0, 0, bounds.width, bounds.height);
                 int textIndent = INDENT;
                 FontMetrics fm = e.gc.getFontMetrics();
                 int height = fm.getHeight();
                 int textMiddle = (bounds.height - height) / 2;
                 e.gc.setForeground(widgetForeground);
                 String properties_not_available = TabbedPropertyMessages.TabbedPropertyList_properties_not_available;
                 e.gc.drawText(properties_not_available, textIndent, textMiddle);
             }

             if (isUpScrollRequired()) {
                 e.gc.setForeground(widgetDarkShadow);
                 int middle = bounds.width / 2;
                 e.gc.drawLine(middle + 1, 3, middle + 5, 7);
                 e.gc.drawLine(middle, 3, middle - 4, 7);
                 e.gc.drawLine(middle - 3, 7, middle + 4, 7);

                 e.gc.setForeground(listBackground);
                 e.gc.drawLine(middle, 4, middle + 1, 4);
                 e.gc.drawLine(middle - 1, 5, middle + 2, 5);
                 e.gc.drawLine(middle - 2, 6, middle + 3, 6);

                 e.gc.setForeground(widgetNormalShadow);
                 e.gc.drawLine(0, 0, bounds.width - 2, 0);
                 e.gc.setForeground(navigationElementShadowStroke);
                 e.gc.drawLine(0, 1, bounds.width - 2, 1);
                 e.gc.drawLine(0, bounds.height - 1, bounds.width - 2,
                         bounds.height - 1);
             }
         }
     }

     /**
      * The top navigation element in the tabbed property list. It looks like a
      * scroll button when scrolling is needed or is just a spacer when no
      * scrolling is required.
      */
     public class BottomNavigationElement extends Canvas {

         /**
          * Constructor for BottomNavigationElement.
          *
          * @param parent
          * the parent Composite.
          */
         public BottomNavigationElement(Composite parent) {
             super(parent, SWT.NO_FOCUS);
             addPaintListener(new PaintListener() {

                 public void paintControl(PaintEvent e) {
                     paint(e);
                 }
             });
             addMouseListener(new MouseAdapter() {

                 public void mouseUp(MouseEvent e) {
                     if (isDownScrollRequired()) {
                         topVisibleIndex++;
                         if (bottomVisibleIndex != elements.length - 1) {
                             bottomVisibleIndex++;
                         }
                         layoutTabs();
                         topNavigationElement.redraw();
                         bottomNavigationElement.redraw();
                     }
                 }
             });
         }

         /**
          * Paint the element.
          *
          * @param e
          * the paint event.
          */
         private void paint(PaintEvent e) {
             e.gc.setBackground(widgetBackground);
             e.gc.setForeground(widgetForeground);
             Rectangle bounds = getBounds();

             if (elements.length != 0) {
                 e.gc.fillRectangle(0, 0, bounds.width, bounds.height);
                 e.gc.setForeground(widgetNormalShadow);
                 e.gc.drawLine(bounds.width - 1, 0, bounds.width - 1,
                         bounds.height - 1);
                 e.gc.drawLine(0, 0, bounds.width - 1, 0);

                 e.gc.setForeground(bottomNavigationElementShadowStroke1);
                 e.gc.drawLine(0, 1, bounds.width - 2, 1);
                 e.gc.setForeground(bottomNavigationElementShadowStroke2);
                 e.gc.drawLine(0, 2, bounds.width - 2, 2);
             } else {
                 e.gc.setBackground(listBackground);
                 e.gc.fillRectangle(0, 0, bounds.width, bounds.height);
             }

             if (isDownScrollRequired()) {
                 e.gc.setForeground(widgetDarkShadow);
                 int middle = bounds.width / 2;
                 int bottom = bounds.height - 3;
                 e.gc.drawLine(middle + 1, bottom, middle + 5, bottom - 4);
                 e.gc.drawLine(middle, bottom, middle - 4, bottom - 4);
                 e.gc.drawLine(middle - 3, bottom - 4, middle + 4, bottom - 4);

                 e.gc.setForeground(listBackground);
                 e.gc.drawLine(middle, bottom - 1, middle + 1, bottom - 1);
                 e.gc.drawLine(middle - 1, bottom - 2, middle + 2, bottom - 2);
                 e.gc.drawLine(middle - 2, bottom - 3, middle + 3, bottom - 3);

                 e.gc.setForeground(widgetNormalShadow);
                 e.gc.drawLine(0, bottom - 7, bounds.width - 2, bottom - 7);
                 e.gc.setForeground(navigationElementShadowStroke);
                 e.gc.drawLine(0, bottom + 2, bounds.width - 2, bottom + 2);
                 e.gc.drawLine(0, bottom - 6, bounds.width - 2, bottom - 6);
             }
         }
     }

     /**
      * Constructor for TabbedPropertyList.
      *
      * @param parent
      * the parent widget.
      * @param factory
      * the widget factory.
      */
     public TabbedPropertyList(Composite parent,
             TabbedPropertySheetWidgetFactory factory) {
         super(parent, SWT.NO_FOCUS);
         this.factory = factory;
         removeAll();
         setLayout(new FormLayout());
         initColours();
         initAccessible();
         topNavigationElement = new TopNavigationElement(this);
         bottomNavigationElement = new BottomNavigationElement(this);

         this.addFocusListener(new FocusListener() {

             public void focusGained(FocusEvent e) {
                 int i = getSelectionIndex();
                 if (i >= 0) {
                     elements[i].redraw();
                 }
             }

             public void focusLost(FocusEvent e) {
                 int i = getSelectionIndex();
                 if (i >= 0) {
                     elements[i].redraw();
                 }
             }
         });
         this.addControlListener(new ControlAdapter() {

             public void controlResized(ControlEvent e) {
                 computeTopAndBottomTab();
             }
         });
         this.addTraverseListener(new TraverseListener() {

             public void keyTraversed(TraverseEvent e) {
                 if (e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
                     || e.detail == SWT.TRAVERSE_ARROW_NEXT) {
                     int nMax = elements.length - 1;
                     int nCurrent = getSelectionIndex();
                     if (e.detail == SWT.TRAVERSE_ARROW_PREVIOUS) {
                         nCurrent -= 1;
                         nCurrent = Math.max(0, nCurrent);
                     } else if (e.detail == SWT.TRAVERSE_ARROW_NEXT) {
                         nCurrent += 1;
                         nCurrent = Math.min(nCurrent, nMax);
                     }
                     select(nCurrent);
                     redraw();
                 } else {
                     e.doit = true;
                 }
             }
         });
     }

     /**
      * Calculate the number of tabs that will fit in the tab list composite.
      */
     protected void computeTabsThatFitInComposite() {
         tabsThatFitInComposite = Math
             .round((getSize().y - 22) / getTabHeight());
         if (tabsThatFitInComposite <= 0) {
             tabsThatFitInComposite = 1;
         }
     }

     /**
      * Returns the element with the given index from this list viewer. Returns
      * <code>null</code> if the index is out of range.
      *
      * @param index
      * the zero-based index
      * @return the element at the given index, or <code>null</code> if the
      * index is out of range
      */
     public Object getElementAt(int index) {
         if (index >= 0 && index < elements.length) {
             return elements[index];
         }
         return null;
     }

     /**
      * Returns the zero-relative index of the item which is currently selected
      * in the receiver, or -1 if no item is selected.
      *
      * @return the index of the selected item
      */
     public int getSelectionIndex() {
         return selectedElementIndex;
     }

     /**
      * Removes all elements from this list.
      */
     public void removeAll() {
         if (elements != null) {
             for (int i = 0; i < elements.length; i++) {
                 elements[i].dispose();
             }
         }
         elements = ELEMENTS_EMPTY;
         selectedElementIndex = NONE;
         widestLabelIndex = NONE;
         topVisibleIndex = NONE;
         bottomVisibleIndex = NONE;
     }

     /**
      * Sets the new list elements.
      *
      * @param children
      */
     public void setElements(Object [] children) {
         if (elements != ELEMENTS_EMPTY) {
             removeAll();
         }
         elements = new ListElement[children.length];
         if (children.length == 0) {
             widestLabelIndex = NONE;
         } else {
             widestLabelIndex = 0;
             for (int i = 0; i < children.length; i++) {
                 elements[i] = new ListElement(this, (ITabItem) children[i], i);
                 elements[i].setVisible(false);
                 elements[i].setLayoutData(null);

                 if (i != widestLabelIndex) {
                     String label = ((ITabItem) children[i]).getText();
                     int width = getTextDimension(label).x;
                     if (((ITabItem) children[i]).getImage() != null) {
                         width = width + 16 + 5;
                     }
                     if (((ITabItem) children[i]).isIndented()) {
                         width = width + INDENT;
                     }
                     if (width > getTextDimension(((ITabItem) children[widestLabelIndex])
                             .getText()).x) {
                         widestLabelIndex = i;
                     }
                 }
             }
         }

         computeTopAndBottomTab();
     }

     /**
      * Selects one of the elements in the list.
      *
      * @param index
      * the index of the element to select.
      */
     protected void select(int index) {
         if (getSelectionIndex() == index) {
             /*
              * this index is already selected.
              */
             return;
         }
         if (index >= 0 && index < elements.length) {
             int lastSelected = getSelectionIndex();
             elements[index].setSelected(true);
             selectedElementIndex = index;
             if (lastSelected != NONE) {
                 elements[lastSelected].setSelected(false);
                 if (getSelectionIndex() != elements.length - 1) {
                     /*
                      * redraw the next tab to fix the border by calling
                      * setSelected()
                      */
                     elements[getSelectionIndex() + 1].setSelected(false);
                 }
             }
             topNavigationElement.redraw();
             bottomNavigationElement.redraw();

             if (selectedElementIndex < topVisibleIndex
                 || selectedElementIndex > bottomVisibleIndex) {
                 computeTopAndBottomTab();
             }
         }
         notifyListeners(SWT.Selection, new Event());
     }

     /**
      * Deselects all the elements in the list.
      */
     public void deselectAll() {
         if (getSelectionIndex() != NONE) {
             elements[getSelectionIndex()].setSelected(false);
             selectedElementIndex = NONE;
         }
     }

     private int getIndex(ListElement element) {
         return element.index;
     }

     public Point computeSize(int wHint, int hHint, boolean changed) {
         Point result = super.computeSize(hHint, wHint, changed);
         if (widestLabelIndex == -1) {
             String properties_not_available = TabbedPropertyMessages.TabbedPropertyList_properties_not_available;
             result.x = getTextDimension(properties_not_available).x + INDENT;
         } else {
             ITabItem widestTab = elements[widestLabelIndex].getTabItem();
             int width = getTextDimension(widestTab.getText()).x + INDENT;
             /*
              * To anticipate for the icon placement we should always keep the
              * space available after the label. So when the active tab includes
              * an icon the width of the tab doesn't change.
              */
             if (widestTab.getImage() != null) {
                 width = width + 16 + 4;
             }
             if (widestTab.isIndented()) {
                 width = width + 10;
             }
             /*
              * Add 10 pixels to the right of the longest string as a margin.
              */
             result.x = width + 10;
         }
         return result;
     }

     /**
      * Get the dimensions of the provided string.
      *
      * @param text
      * the string.
      * @return the dimensions of the provided string.
      */
     private Point getTextDimension(String text) {
         Shell shell = new Shell();
         GC gc = new GC(shell);
         gc.setFont(JFaceResources.getFontRegistry().getBold(
                 JFaceResources.DEFAULT_FONT));
         Point point = gc.textExtent(text);
         point.x++;
         gc.dispose();
         shell.dispose();
         return point;
     }

     /**
      * Initialize the colours used in the list.
      */
     private void initColours() {
         /*
          * Colour 3 COLOR_LIST_BACKGROUND
          */
         listBackground = Display.getCurrent().getSystemColor(
                 SWT.COLOR_LIST_BACKGROUND);

         /*
          * Colour 13 COLOR_WIDGET_BACKGROUND
          */
         widgetBackground = Display.getCurrent().getSystemColor(
                 SWT.COLOR_WIDGET_BACKGROUND);

         /*
          * Colour 15 COLOR_WIDGET_DARK_SHADOW
          */
         widgetDarkShadow = Display.getCurrent().getSystemColor(
                 SWT.COLOR_WIDGET_DARK_SHADOW);

         /*
          * Colour 16 COLOR_WIDGET_FOREGROUND
          */
         widgetForeground = Display.getCurrent().getSystemColor(
                 SWT.COLOR_WIDGET_FOREGROUND);

         /*
          * Colour 19 COLOR_WIDGET_NORMAL_SHADOW
          */
         widgetNormalShadow = Display.getCurrent().getSystemColor(
                 SWT.COLOR_WIDGET_NORMAL_SHADOW);

         RGB infoBackground = Display.getCurrent().getSystemColor(
                 SWT.COLOR_INFO_BACKGROUND).getRGB();
         RGB white = Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)
                 .getRGB();
         RGB black = Display.getCurrent().getSystemColor(SWT.COLOR_BLACK)
                 .getRGB();

         /*
          * gradient in the default tab: start colour WIDGET_NORMAL_SHADOW 100% +
          * white 20% + INFO_BACKGROUND 60% end colour WIDGET_NORMAL_SHADOW 100% +
          * INFO_BACKGROUND 40%
          */
         defaultGradientStart = factory.getColors().createColor(
                 "TabbedPropertyList.defaultTabGradientStart", //$NON-NLS-1$
 FormColors.blend(infoBackground, FormColors.blend(white,
                         widgetNormalShadow.getRGB(), 20), 60));
         defaultGradientEnd = factory.getColors().createColor(
                 "TabbedPropertyList.defaultTabGradientEnd", //$NON-NLS-1$
 FormColors.blend(infoBackground, widgetNormalShadow.getRGB(),
                         40));

         navigationElementShadowStroke = factory.getColors().createColor(
                 "TabbedPropertyList.shadowStroke", //$NON-NLS-1$
 FormColors.blend(white, widgetNormalShadow.getRGB(), 55));
         bottomNavigationElementShadowStroke1 = factory.getColors().createColor(
                 "TabbedPropertyList.tabShadowStroke1", //$NON-NLS-1$
 FormColors.blend(black, widgetBackground.getRGB(), 10));
         bottomNavigationElementShadowStroke2 = factory.getColors().createColor(
                 "TabbedPropertyList.tabShadowStroke2", //$NON-NLS-1$
 FormColors.blend(black, widgetBackground.getRGB(), 5));

         /*
          * gradient in the hover tab: start colour WIDGET_BACKGROUND 100% +
          * white 20% end colour WIDGET_BACKGROUND 100% + WIDGET_NORMAL_SHADOW
          * 10%
          */
         hoverGradientStart = factory.getColors().createColor(
                 "TabbedPropertyList.hoverBackgroundGradientStart", //$NON-NLS-1$
 FormColors.blend(white, widgetBackground.getRGB(), 20));
         hoverGradientEnd = factory.getColors().createColor(
                 "TabbedPropertyList.hoverBackgroundGradientEnd", //$NON-NLS-1$
 FormColors.blend(widgetNormalShadow.getRGB(), widgetBackground
                         .getRGB(), 10));

         indentedDefaultBackground = factory.getColors().createColor(
                 "TabbedPropertyList.indentedDefaultBackground", //$NON-NLS-1$
 FormColors.blend(white, widgetBackground.getRGB(), 10));
         indentedHoverBackground = factory.getColors().createColor(
                 "TabbedPropertyList.indentedHoverBackground", //$NON-NLS-1$
 FormColors.blend(white, widgetBackground.getRGB(), 75));
     }

     public void dispose() {
         hoverGradientStart.dispose();
         hoverGradientEnd.dispose();
         defaultGradientStart.dispose();
         defaultGradientEnd.dispose();
         indentedDefaultBackground.dispose();
         indentedHoverBackground.dispose();
         navigationElementShadowStroke.dispose();
         bottomNavigationElementShadowStroke1.dispose();
         bottomNavigationElementShadowStroke2.dispose();
         super.dispose();
     }

     /**
      * Get the height of a tab. The height of the tab is the height of the text
      * plus buffer.
      *
      * @return the height of a tab.
      */
     private int getTabHeight() {
         int tabHeight = getTextDimension("").y + INDENT; //$NON-NLS-1$
 if (tabsThatFitInComposite == 1) {
             /*
              * if only one tab will fix, reduce the size of the tab height so
              * that the navigation elements fit.
              */
             int ret = getBounds().height - 20;
             return (ret > tabHeight) ? tabHeight
                 : (ret < 5) ? 5
                     : ret;
         }
         return tabHeight;
     }

     /**
      * Determine if a downward scrolling is required.
      *
      * @return true if downward scrolling is required.
      */
     private boolean isDownScrollRequired() {
         return elements.length > tabsThatFitInComposite
             && bottomVisibleIndex != elements.length - 1;
     }

     /**
      * Determine if an upward scrolling is required.
      *
      * @return true if upward scrolling is required.
      */
     private boolean isUpScrollRequired() {
         return elements.length > tabsThatFitInComposite && topVisibleIndex != 0;
     }

     /**
      * Based on available space, figure out the top and bottom tabs in the list.
      */
     private void computeTopAndBottomTab() {
         computeTabsThatFitInComposite();
         if (elements.length == 0) {
             /*
              * no tabs to display.
              */
             topVisibleIndex = 0;
             bottomVisibleIndex = 0;
         } else if (tabsThatFitInComposite >= elements.length) {
             /*
              * all the tabs fit.
              */
             topVisibleIndex = 0;
             bottomVisibleIndex = elements.length - 1;
         } else if (getSelectionIndex() == NONE) {
             /*
              * there is no selected tab yet, assume that tab one would
              * be selected for now.
              */
             topVisibleIndex = 0;
             bottomVisibleIndex = tabsThatFitInComposite - 1;
         } else if (getSelectionIndex() + tabsThatFitInComposite > elements.length) {
             /*
              * the selected tab is near the bottom.
              */
             bottomVisibleIndex = elements.length - 1;
             topVisibleIndex = bottomVisibleIndex - tabsThatFitInComposite + 1;
         } else {
             /*
              * the selected tab is near the top.
              */
             topVisibleIndex = selectedElementIndex;
             bottomVisibleIndex = selectedElementIndex + tabsThatFitInComposite
                 - 1;
         }
         layoutTabs();
     }

     /**
      * Layout the tabs.
      *
      * @param up
      * if <code>true</code>, then we are laying out as a result of
      * an scroll up request.
      */
     private void layoutTabs() {
         //System.out.println("TabFit " + tabsThatFitInComposite + " length "
 // + elements.length + " top " + topVisibleIndex + " bottom "
 // + bottomVisibleIndex);
 if (tabsThatFitInComposite == NONE || elements.length == 0) {
             FormData formData = new FormData();
             formData.left = new FormAttachment(0, 0);
             formData.right = new FormAttachment(100, 0);
             formData.top = new FormAttachment(0, 0);
             formData.height = getTabHeight();
             topNavigationElement.setLayoutData(formData);

             formData = new FormData();
             formData.left = new FormAttachment(0, 0);
             formData.right = new FormAttachment(100, 0);
             formData.top = new FormAttachment(topNavigationElement, 0);
             formData.bottom = new FormAttachment(100, 0);
             bottomNavigationElement.setLayoutData(formData);
         } else {

             FormData formData = new FormData();
             formData.left = new FormAttachment(0, 0);
             formData.right = new FormAttachment(100, 0);
             formData.top = new FormAttachment(0, 0);
             formData.height = 10;
             topNavigationElement.setLayoutData(formData);

             /*
              * use nextElement to attach the layout to the previous canvas
              * widget in the list.
              */
             Canvas nextElement = topNavigationElement;

             for (int i = 0; i < elements.length; i++) {
                 //System.out.print(i + " [" + elements[i].getText() + "]");
 if (i < topVisibleIndex || i > bottomVisibleIndex) {
                     /*
                      * this tab is not visible
                      */
                     elements[i].setLayoutData(null);
                     elements[i].setVisible(false);
                 } else {
                     /*
                      * this tab is visible.
                      */
                     //System.out.print(" visible");
 formData = new FormData();
                     formData.height = getTabHeight();
                     formData.left = new FormAttachment(0, 0);
                     formData.right = new FormAttachment(100, 0);
                     formData.top = new FormAttachment(nextElement, 0);
                     nextElement = elements[i];
                     elements[i].setLayoutData(formData);
                     elements[i].setVisible(true);
                 }

                 //if (i == selectedElementIndex) {
 // System.out.print(" selected");
 //}
 //System.out.println("");
 }
             formData = new FormData();
             formData.left = new FormAttachment(0, 0);
             formData.right = new FormAttachment(100, 0);
             formData.top = new FormAttachment(nextElement, 0);
             formData.bottom = new FormAttachment(100, 0);
             formData.height = 10;
             bottomNavigationElement.setLayoutData(formData);
         }
         //System.out.println("");

         // layout so that we have enough space for the new labels
 Composite grandparent = getParent().getParent();
         grandparent.layout(true);
         layout(true);
     }

     /**
      * Initialize the accessibility adapter.
      */
     private void initAccessible() {
         final Accessible accessible = getAccessible();
         accessible.addAccessibleListener(new AccessibleAdapter() {

             public void getName(AccessibleEvent e) {
                 if (getSelectionIndex() != NONE) {
                     e.result = elements[getSelectionIndex()].getTabItem()
                             .getText();
                 }
             }

             public void getHelp(AccessibleEvent e) {
                 if (getSelectionIndex() != NONE) {
                    e.result = elements[getSelectionIndex()].getTabItem()
                            .getText();
                }
            }
        });

        accessible.addAccessibleControlListener(new AccessibleControlAdapter() {

            public void getChildAtPoint(AccessibleControlEvent e) {
                Point pt = toControl(new Point(e.x, e.y));
                e.childID = (getBounds().contains(pt)) ? ACC.CHILDID_SELF
                    : ACC.CHILDID_NONE;
            }

            public void getLocation(AccessibleControlEvent e) {
                if (getSelectionIndex() != NONE) {
                    Rectangle location = elements[getSelectionIndex()]
                        .getBounds();
                    Point pt = toDisplay(new Point(location.x, location.y));
                    e.x = pt.x;
                    e.y = pt.y;
                    e.width = location.width;
                    e.height = location.height;
                }
            }

            public void getChildCount(AccessibleControlEvent e) {
                e.detail = 0;
            }

            public void getRole(AccessibleControlEvent e) {
                e.detail = ACC.ROLE_TABITEM;
            }

            public void getState(AccessibleControlEvent e) {
                e.detail = ACC.STATE_NORMAL | ACC.STATE_SELECTABLE
                    | ACC.STATE_SELECTED | ACC.STATE_FOCUSED
                    | ACC.STATE_FOCUSABLE;
            }
        });

        addListener(SWT.Selection, new Listener() {

            public void handleEvent(Event event) {
                if (isFocusControl()) {
                    accessible.setFocus(ACC.CHILDID_SELF);
                }
            }
        });

        addListener(SWT.FocusIn, new Listener() {

            public void handleEvent(Event event) {
                accessible.setFocus(ACC.CHILDID_SELF);
            }
        });
    }
}

